package com.example.demo.util.httpclient;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Executor
 *
 * @author hao.chen
 * @date 2023/6/3 19:54
 */
@Slf4j
public class Executor {
    private final HttpClient client;
    private final HttpRequestBase request;
    // 请求内容类型
    private final BodyType bodyType;
    // 请求资源地址
    private String url;

    // 字符集编码 默认：UTF-8
    private Charset charset = Consts.UTF_8;
    // 读取超时间 默认：30s
    private Integer socketTimeout = 30 * 1000;
    // 连接超时时间 默认：10s
    private Integer connectTimeout = 10 * 1000;
    // 连接池获取连接超时时间 默认：3s
    private Integer connectionRequestTimeout = 3 * 1000;

    // 头信息
    private List<Header> headers;
    // 参数
    private Map<String, Object> params;
    // 原生内容
    private String raw;
    // 输入流
    private File file;
    // 输入流
    private InputStream inputStream;
    // 文件名
    private String fileName;
    // 标签
    private String tag = "";

    // 是否开始Gzip压缩
    private boolean onGzip = false;
    // 是否打印返回内容
    private boolean logContent = false;

    public Executor(HttpClient client, HttpRequestBase request, BodyType bodyType) {
        this.client = client;
        this.request = request;
        this.bodyType = bodyType;
        this.url = request.getURI().toString();
    }

    public Executor charset(Charset charset) {
        this.charset = charset;
        return this;
    }

    public Executor socketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
        return this;
    }

    public Integer getSocketTimeout() {
        return socketTimeout;
    }

    public Executor connectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public Integer getConnectTimeout() {
        return connectTimeout;
    }

    public Executor connectionRequestTimeout(int connectionRequestTimeout) {
        this.connectionRequestTimeout = connectionRequestTimeout;
        return this;
    }

    public Executor headers(Map<String, String> headers) {
        if (MapUtils.isEmpty(headers)) {
            return this;
        }
        if (this.headers == null) {
            this.headers = Lists.newArrayList();
        }
        headers.forEach((name, value) -> this.headers.add(new BasicHeader(name, value)));
        return this;
    }

    public Executor header(String name, String value) {
        if (this.headers == null) {
            this.headers = Lists.newArrayList();
        }
        this.headers.add(new BasicHeader(name, value));
        return this;
    }

    public Executor params(Map<String, Object> params) {
        this.params = params;
        return this;
    }

    public Executor param(String name, Object value) {
        if (this.params == null) {
            this.params = Maps.newHashMap();
        }
        params.put(name, value);
        return this;
    }

    public Map<String, Object> getParams() {
        return params;
    }

    public Executor raw(String raw) {
        this.raw = raw;
        return this;
    }

    public Executor file(File file) {
        this.file = file;
        return this;
    }

    public Executor inputStream(InputStream inputStream) {
        this.inputStream = inputStream;
        return this;
    }

    public Executor fileName(String fileName) {
        this.fileName = fileName;
        return this;
    }

    public Executor tag(String tag) {
        this.tag = tag;
        return this;
    }

    public Executor enableGzip() {
        this.onGzip = true;
        return this;
    }

    public Executor logContent() {
        this.logContent = true;
        return this;
    }

    public StringResponse execute() throws Exception {
        // 设置config
        setConfig();
        // 设置header
        setHeader();
        // 设置body
        setBody();

        StringResponse stringResponse = new StringResponse();
        long timeMillis = System.currentTimeMillis();
        try {
            // 打印请求
            logRequest();
            // 执行请求
            HttpResponse response = client.execute(request);
            // 解析结果
            stringResponse = handleResponse(response);
        } catch (Exception e) {
            stringResponse = new StringResponse(e);
            throw e;
        } finally {
            // 统计耗时
            timeMillis = System.currentTimeMillis() - timeMillis;
            stringResponse.setCostTime(timeMillis);
            // 打印响应
            logResponse(timeMillis, stringResponse);
        }
        return stringResponse;
    }

    /**
     * 设置config
     */
    private void setConfig() {
        RequestConfig.Builder builder = RequestConfig.custom();
        // 设置连接超时时间
        if (connectTimeout > 0) {
            builder.setConnectTimeout(connectTimeout);
        }
        // 设置读取超时时间
        if (socketTimeout > 0) {
            builder.setSocketTimeout(socketTimeout);
        }
        // 设置连接池获取连接超时时间
        if (connectionRequestTimeout > 0) {
            builder.setConnectionRequestTimeout(connectionRequestTimeout);
        }
        RequestConfig config = builder.build();
        request.setConfig(config);
    }

    /**
     * 设置header
     */
    private void setHeader() {
        if (CollectionUtils.isNotEmpty(headers)) {
            request.setHeaders(headers.toArray(new Header[0]));
        }
        request.setHeader(HttpHeaders.CONNECTION, HTTP.CONN_KEEP_ALIVE);
        if (this.onGzip) {
            request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
        }
    }

    /**
     * 设置body
     */
    private void setBody() throws IOException {
        HttpEntity reqEntity = null;
        if (bodyType == BodyType.FORM) {
            if (MapUtils.isNotEmpty(params)) {
                reqEntity = new UrlEncodedFormEntity(Objects.requireNonNull(map2NameValuePairList(params)), charset);
                if (request instanceof HttpGet) {
                    if (url.contains("?")) {
                        if (url.endsWith("?")) {
                            url = url + EntityUtils.toString(reqEntity);
                        } else {
                            url = url + "&" + EntityUtils.toString(reqEntity);
                        }
                    } else {
                        url = url + "?" + EntityUtils.toString(reqEntity);
                    }
                    request.setURI(URI.create(url));
                }
            }
        } else if (bodyType == BodyType.JSON) {
            ContentType contentType = ContentType.APPLICATION_JSON.withCharset(charset);
            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType.toString());
            reqEntity = new StringEntity(raw, contentType);
        } else if (bodyType == BodyType.XML) {
            ContentType contentType = ContentType.APPLICATION_XML.withCharset(charset);
            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType.toString());
            reqEntity = new StringEntity(raw, contentType);
        } else if (bodyType == BodyType.FILE) {
            if (file == null && inputStream == null) {
                throw new NullPointerException("file or inputStream can't be null");
            }
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setCharset(charset);
            if (file != null) {
                builder.addBinaryBody("file", file, ContentType.DEFAULT_BINARY, file.getName());
            } else if (inputStream != null) {
                if (StringUtils.isEmpty(fileName)) {
                    throw new NullPointerException("fileName can't be empty");
                }
                // 注意：文件类型是输入流时fileName不能为空
                builder.addBinaryBody("file", inputStream, ContentType.DEFAULT_BINARY, fileName);
            }
            if (MapUtils.isNotEmpty(params)) {
                ContentType contentType = ContentType.TEXT_PLAIN.withCharset(Charset.defaultCharset());
                params.forEach((key, value) -> {
                    StringBody stringBody = new StringBody(value.toString(), contentType);
                    builder.addPart(key, stringBody);
                });
            }
            reqEntity = builder.build();
        }
        if (reqEntity != null && request instanceof HttpEntityEnclosingRequestBase) {
            ((HttpEntityEnclosingRequestBase) request).setEntity(reqEntity);
        }
    }

    /**
     * 解析结果
     */
    private StringResponse handleResponse(HttpResponse response) throws IOException {
        HttpEntity resEntity = response.getEntity();
        String contentString = null;
        if (resEntity != null) {
            if (resEntity.getContentEncoding() != null && "gzip".equals(resEntity.getContentEncoding().getValue())) {
                resEntity = new GzipDecompressingEntity(resEntity);
            }
            contentString = EntityUtils.toString(resEntity, charset);
        }
        EntityUtils.consume(resEntity);
        StatusLine statusLine = response.getStatusLine();
        int statusCode = statusLine.getStatusCode();
        String reasonPhrase = statusLine.getReasonPhrase();
        if (StringUtils.isBlank(reasonPhrase)) {
            HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
            if (httpStatus != null) {
                reasonPhrase = httpStatus.getReasonPhrase();
            }
        }
        statusLine = new BasicStatusLine(statusLine.getProtocolVersion(), statusCode, reasonPhrase);
        return new StringResponse(contentString, statusLine);
    }

    /**
     * 打印请求
     */
    private void logRequest() {
        StringBuilder logSb = new StringBuilder();
        logSb.append("Request  >> ");
        logSb.append("method: ").append(request.getMethod());
        logSb.append(" - url: ").append(url);
        if (bodyType == BodyType.JSON || bodyType == BodyType.XML) {
            logSb.append(" - body: ").append(raw);
        }
        if (StringUtils.isNotEmpty(tag)) {
            logSb.append(" - tag: ").append(tag);
        }
        log.info(logSb.toString());
    }

    /**
     * 打印响应
     */
    private void logResponse(long timeMillis, StringResponse stringResponse) {
        StringBuilder logSb = new StringBuilder();
        logSb.append("Response << ");
        logSb.append("time: ").append(timeMillis);
        if (stringResponse.getStatusLine() != null) {
            logSb.append(" - status: ").append(stringResponse.getStatusLine().getStatusCode());
            logSb.append(" - reason: ").append(stringResponse.getStatusLine().getReasonPhrase());
        }
        if (stringResponse.getException() != null) {
            logSb.append(" - exception: ").append(ExceptionUtils.getRootCauseMessage(stringResponse.getException()));
        }
        if (logContent) {
            logSb.append(" - content: ").append(stringResponse.getContentString());
        }
        if (StringUtils.isNotEmpty(tag)) {
            logSb.append(" - tag: ").append(tag);
        }
        log.info(logSb.toString());
    }

    /**
     * 参数转换
     */
    private List<NameValuePair> map2NameValuePairList(Map<String, ?> params) {
        if (MapUtils.isNotEmpty(params)) {
            List<NameValuePair> list = new ArrayList<>();
            params.forEach((k, v) -> {
                if (v != null) {
                    list.add(new BasicNameValuePair(k, String.valueOf(v)));
                }
            });
            return list;
        }
        return null;
    }
}
